﻿#region Using Statements
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Media;
using starLiGHT.ParticleSystem.Common;
using starLiGHT.ParticleSystem.twoD;

using ActiveParticleList = System.Collections.Generic.LinkedList<starLiGHT.ParticleSystem.Common.Particle>;
using FreeParticleList = System.Collections.Generic.LinkedList<starLiGHT.ParticleSystem.Common.Particle>;
using ParticlePool = starLiGHT.Collections.FastList<starLiGHT.ParticleSystem.Common.Particle>;

#endregion

#region Version Stuff
// **************[ starLiGHT.Engine SVN ]**********************
// * $Rev:: 1383                  $:  Revision of last commit *
// * $Author:: glatzemann         $:  Author of last commit   *
// * $Date:: 2010-09-06 19:53:00 #$:  Date of last commit     *
// ************************************************************
#endregion

namespace starLiGHT.SceneManagement
{
    public class ParticleSystem ////////TODO: : MovableObject2D, IRenderable2D
    {
        #region Member Variables
        private RectangleF mAABB;
        private float mBoundingRadius;
        private bool mBoundsAutoUpdate;
        private float mBoundsUpdateTime;
        private float mUpdateRemainTime;

        private RectangleF mWorldAABB;

        private bool mIsRendererConfigured;

        private float mSpeedFactor;
        private float mIterationInterval;
        private bool mIterationIntervalSet;
        private bool mSorted;

        private float mNonvisibleTimeout;
        private bool mNonvisibleTimeoutSet;
        private double mTimeSinceLastVisible;
        private long mLastVisibleFrame;

        private bool mIsEmitting;

        private int mQuota;

        private ActiveParticleList mActiveParticles = new ActiveParticleList();
        private FreeParticleList mFreeParticles = new FreeParticleList();
        private ParticlePool mParticlePool = new ParticlePool();

        private IRenderer mRenderer;

        private bool mCullIndividual;
        private string mRendererType;
        private int mPoolSize;

        private static float msDefaultIterationInterval = 0;
        private static float msDefaultNonvisibleTimeout = 0;

        #endregion

        #region Constructors
        public ParticleSystem(GraphicsDevice graphics)
            : base()
        {
            mAABB = RectangleF.Empty;
            mBoundingRadius = 1.0f;
            mBoundsAutoUpdate = true;
            mBoundsUpdateTime = 10.0f;
            mUpdateRemainTime = 0.0f;
            mWorldAABB = RectangleF.Empty;

            mIsRendererConfigured = false;
            mSpeedFactor = 1.0f;
            mIterationInterval = 0;
            mIterationIntervalSet = false;
            mSorted = false;

            mNonvisibleTimeout = 0;
            mNonvisibleTimeoutSet = false;
            mTimeSinceLastVisible = 0;
            mLastVisibleFrame = 0;

            mIsEmitting = true;
            mRenderer = null;

            initParameters();

            setRenderer("RenderTargetRenderer", graphics);
        }

        public ParticleSystem(string name, string resourceGroup, GraphicsDevice graphics)
            ////////TODO: : base(name)
        {
            mAABB = RectangleF.Empty;
            mBoundingRadius = 1.0f;
            mBoundsAutoUpdate = true;
            mBoundsUpdateTime = 10.0f;
            mUpdateRemainTime = 0.0f;
            mWorldAABB = RectangleF.Empty;

            mIsRendererConfigured = false;
            mSpeedFactor = 1.0f;
            mIterationInterval = 0;
            mIterationIntervalSet = false;
            mSorted = false;

            mNonvisibleTimeout = 0;
            mNonvisibleTimeoutSet = false;
            mTimeSinceLastVisible = 0;
            mLastVisibleFrame = 0;

            mIsEmitting = true;
            mRenderer = null;

            initParameters();

            setRenderer("RenderTargetRenderer", graphics);
        }

        #endregion

        private void initParameters()
        {
            ////////TODO: implement
        }

        public void StartEmitters()
        {
            if (mRenderer != null)
            {
                foreach (Emitter e in mRenderer.Emitters.Values)
                {
                    e.start();
                }
            }
        }

        public void setRenderer(string typeName, GraphicsDevice graphics)
        {
            if (mRenderer != null)
            {
                // Destroy existing
                ////////TODO: destroyVisualParticles(0, mParticlePool.Count);
#if !XNA4
                ParticleSystemManager.Instance._destroyRenderer(mRenderer);
#endif
                mRenderer = null;
            }

            if (!String.IsNullOrEmpty(typeName))
            {
#if !XNA4
                mRenderer = ParticleSystemManager.Instance._createRenderer(typeName, graphics);
#endif
                mIsRendererConfigured = false;
            }
        }

        private void configureRenderer()
        {
            //////////TODO: implement
            ////// Actual allocate particles
            ////int currSize = mParticlePool.Count;
            ////int size = mPoolSize;
            ////if (currSize < size)
            ////{
            ////    increasePool(size);

            ////    for (int i = currSize; i < size; ++i)
            ////    {
            ////        // Add new items to the queue
            ////        mFreeParticles.AddLast(mParticlePool[i]);
            ////    }

            ////    // Tell the renderer, if already configured
            ////    if (mRenderer != null && mIsRendererConfigured)
            ////    {
            ////        mRenderer._notifyParticleQuota(size);
            ////    }
            ////}

            ////if (mRenderer != null && !mIsRendererConfigured)
            ////{
            ////    mRenderer._notifyParticleQuota(mParticlePool.Count);
            ////    mRenderer._notifyAttached(mParentNode, mParentIsTagPoint);
            ////    mRenderer._notifyDefaultDimensions(mDefaultWidth, mDefaultHeight);
            ////    createVisualParticles(0, mParticlePool.Count);
            ////    Material2D mat = Core.Instance.MaterialManager.load(mMaterialName, mResourceGroupName);
            ////    mRenderer._setMaterial(mat);
            ////    if (mRenderQueueIDSet)
            ////        mRenderQueueIDSet.setRenderQueueGroup(mRenderQueueID);
            ////    mRenderer.setKeepParticlesInLocalSpace(mLocalSpace);
            ////    mIsRendererConfigured = true;
            ////}
        }

        public IRenderer getRenderer()
        {
            return mRenderer;
        }

        public string getRendererName()
        {
            if (mRenderer != null)
                return mRenderer.Type;
            else
                return String.Empty;
        }

        public Emitter2D addEmitter(Emitter2D emitter)
        {
            if (!mRenderer.Emitters.ContainsKey(emitter.Name))
            {
                // emitter = new Emitter2D();
                // emitter.Name = emitter.Name;
                mRenderer.Emitters[emitter.Name] = emitter;
            }

            return emitter;
        }

        public Emitter2D addEmitter(Emitter2D emitter, string name)
        {
            if (!mRenderer.Emitters.ContainsKey(name))
            {
                emitter.Name = name;
                mRenderer.Emitters[name] = emitter;
            }

            return emitter;
        }

        public Emitter2D addEmitter(string name)
        {
            Emitter2D emitter = null;

            if (!mRenderer.Emitters.ContainsKey(name))
            {
                emitter = new Emitter2D();
                emitter.Name = name;
                mRenderer.Emitters[name] = emitter;
            }

            return emitter;
        }

        public Emitter2D getEmitter(string name)
        {
            Emitter emitter = null;
            mRenderer.Emitters.TryGetValue(name, out emitter);
            return emitter as Emitter2D;
        }

        public int NumEmitter
        {
            get { return mRenderer.Emitters.Count; }
        }

        public void removeEmitter(string name)
        {
            if (mRenderer.Emitters.ContainsKey(name))
                mRenderer.removeEmitter(mRenderer.Emitters[name]);
        }

        public void removeEmitter(Emitter2D emitter)
        {
            mRenderer.removeEmitter(emitter);

            ////emitters.Remove(emitter.Name);
        }

        public void removeAllEmitters()
        {
            while (NumEmitter > 0)
            {
                Emitter2D emitter = mRenderer.Emitters.ElementAt(0).Value as Emitter2D;
                removeEmitter(emitter);
            }
        }

        private void increasePool(int size)
        {
            int oldSize = mParticlePool.Count;

            for (int i = oldSize; i < size; i++)
            {
                mParticlePool.Add(new Particle2D());
            }

            if (mIsRendererConfigured)
            {
                ////TODO: createVisualParticles(oldSize, size);
            }
        }

        public int ParticleQuota
        {
            get { return mQuota; }
            set { mQuota = value; }
        }

        public void _update(float timeElapsed)
        {
            // Only update if attached to a node
            ////TODO: if (mParentNode == null) return;

            float nonvisibleTimeout = mNonvisibleTimeoutSet ? mNonvisibleTimeout : msDefaultNonvisibleTimeout;

            if (nonvisibleTimeout > 0)
            {
                // Check whether it's been more than one frame (update is ahead of
                // camera notification by one frame because of the ordering)
                float frameDiff = 0; ////////TODO: Core.Instance.NextFrameNumber - mLastVisibleFrame;
                if (frameDiff > 1 || frameDiff < 0) // < 0 if wrap only
                {
                    mTimeSinceLastVisible += timeElapsed;
                    if (mTimeSinceLastVisible >= nonvisibleTimeout)
                    {
                        // No update
                        return;
                    }
                }
            }

            // Scale incoming speed for the rest of the calculation
            timeElapsed *= mSpeedFactor;

            // Init renderer if not done already
            configureRenderer();

            // Initialise emitted emitters list if not done already
            ////////TODO: initialiseEmittedEmitters();

            float iterationInterval = mIterationIntervalSet ? mIterationInterval : msDefaultIterationInterval;
            if (iterationInterval > 0)
            {
                mUpdateRemainTime += timeElapsed;

                while (mUpdateRemainTime >= iterationInterval)
                {
                    // Update existing particles
                    ////////TODO: _expire(iterationInterval);
                    ////////TODO: _triggerAffectors(iterationInterval);
                    ////////TODO: _applyMotion(iterationInterval);

                    if (mIsEmitting)
                    {
                        // Emit new particles
                        ////////TODO: _triggerEmitters(iterationInterval);
                    }

                    mUpdateRemainTime -= iterationInterval;
                }
            }
            else
            {
                // Update existing particles
                ////////TODO: _expire(timeElapsed);
                ////////TODO: _triggerAffectors(timeElapsed);
                ////////TODO: _applyMotion(timeElapsed);

                if (mIsEmitting)
                {
                    // Emit new particles
                    ////////TODO: _triggerEmitters(timeElapsed);
                }
            }

            if (!mBoundsAutoUpdate && mBoundsUpdateTime > 0.0f)
                mBoundsUpdateTime -= timeElapsed; // count down
            ////////TODO: _updateBounds();
        }

        ////public override void _notifyCurrentCamera(Camera2D cam)
        ////{
        ////    base._notifyCurrentCamera(cam);

        ////    throw new NotImplementedException();

        ////    //if (IsVisible())
        ////    //{
        ////    //    mLastVisibleFrame = Core.Instance.NextFrameNumber;
        ////    //    mTimeSinceLastVisible = 0.0f;

        ////    //    if (mSorted)
        ////    //    {
        ////    //        _sortParticles(cam);
        ////    //    }

        ////    //    if (mRenderer)
        ////    //    {
        ////    //        if (!mIsRendererConfigured)
        ////    //            configureRenderer();

        ////    //        mRenderer._notifyCurrentCamera(cam);
        ////    //    }
        ////    //}
        ////}

        ////public override void _notifyAttached(Node2D parent, bool isTagPoint)
        ////{
        ////    base._notifyAttached(parent, isTagPoint);

        ////    throw new NotImplementedException();

        ////    //if (mRenderer != null && mIsRendererConfigured)
        ////    //{
        ////    //    mRenderer._notifyAttached(parent, isTagPoint);
        ////    //}

        ////    //if (parent != null && mTimeController == null)
        ////    //{
        ////    //    // Assume visible
        ////    //    mTimeSinceLastVisible = 0;
        ////    //    mLastVisibleFrame = Core.Instance.NextFrameNumber;

        ////    //    // Create time controller when attached
        ////    //    //ControllerManager mgr = ControllerManager.Instance;
        ////    //    ////////TODO: implement
        ////    //}
        ////    //else if (parent == null && mTimeController != null)
        ////    //{
        ////    //    ////////TODO: ControllerManager.Instance.destroyController(mTimeController);
        ////    //    ////////TODO: mTimeController = null;
        ////    //}
        ////}

        ////public override RectangleF BoundingBox
        ////{
        ////    get { throw new NotImplementedException(); }
        ////}

        ////public override float BoundingRadius
        ////{
        ////    get { throw new NotImplementedException(); }
        ////}

        ////public override void _updateRenderQueue(RenderQueue2D queue)
        ////{
        ////    throw new NotImplementedException();
        ////}

        ////public override string MovableType
        ////{
        ////    get { throw new NotImplementedException(); }
        ////}

        ////public Material2D Material
        ////{
        ////    get { throw new NotImplementedException(); }
        ////}

        ////public Technique2D Technique
        ////{
        ////    get { throw new NotImplementedException(); }
        ////}

        ////public bool CastsShadows
        ////{
        ////    get { throw new NotImplementedException(); }
        ////}

        ////public void getRenderOperation(out RenderOperation ro)
        ////{
        ////    throw new NotImplementedException();
        ////}
    }
}
